import {AIConfig, LayaAPIAction, LayaAPICode, LayaAPIStatus} from "./AIConfig";
import { AIUtils, reportQueryError } from "./AIUtils";
import {IDEAgent} from "./IDEAgent";
import {LayaAPI} from "./LayaAPI";
import fs from 'fs'
import path from 'path'
import { 服务过期, 点数不够 } from "./StoreServer";
import { Thinkingdata } from "./Thinkingdata";

export interface ISDAPIBody {
    prompt?: string;
    width?: number;
    height?: number;
    negative_prompt?: string;
    prompt_strength ?: number;
    num_inference_steps ?: number;
    seed ?: number;
    script_name?:string;
    script_args?:any[];
    batch_size?:number;
    alwayson_scripts?:any;
    // 是否使用向量数据库
    reuse?:boolean,
    trans?:boolean
}

export interface ISDImg2ImgReq extends ISDAPIBody {
    init_images:string[];  //base64编码的图片列表
    mask ?:string;    // inpainting用
    cfg_scale?:number;
    denoising_strength?:number;
}


export class SkyBoxGenData{
    prompt: string='广阔的大自然场景，山峦，草原，蓝天，白云';
    template: string = '1';     // （0:indoor|1:outdoor）:
    batch_size: string = "1";   // 每批生成数量(1-4):
    depth: string = "0";        // 是否生成深度图（0|1）:
    reuse: boolean = true;      // 是否使用向量数据库
    trans: boolean = true;
}


/**
 * 给地址url发送一个post请求，参数是一个json对象
 */
export function post(url: string, data: any, responseType:XMLHttpRequestResponseType='text') {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('POST', url, true);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.send(JSON.stringify(data));
        xhr.responseType=responseType;
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4) {
                if (xhr.status == 200) {
                    if(responseType=='blob'||responseType=='arraybuffer')
                        resolve(xhr.response)
                    else
                        resolve(xhr.responseText);
                } else {
                    if(responseType=='blob'||responseType=='arraybuffer'){
                        let err = (new TextDecoder()).decode(xhr.response);
                        console.error(err);
                        resolve(null);
                    }else
                        console.error(xhr.responseText);
                        resolve(null);
                }
            }
        }
    });
}

/**
 * url是一个以stream方式返回数据的接口，data是post的数据，callback是每次返回的数据的回调。
 * 目前只用在gpt的流式返回端口上 /chat
 * @param url
 * @param data
 * @param callback
 */
async function postStream(url: string, data: any, callback: (str: string) => void) {
    const abortCtrl = new AbortController();    // 用来中断fetch
    const signal = abortCtrl.signal;

    try {
        const response = await fetch(url, {
            signal,
            method: 'POST',
            headers: {
                'Content-Type': 'application/json'
            },
            body: JSON.stringify(data)
        });

        if (response.status === 200) {
            if (response.body) {
                let reader = response.body.getReader();
                while (true) {
                    let { done, value } = await reader.read()
                    if (done) {
                        break;
                    }
                    // value for fetch streams is a Uint8Array
                    let end = false;
                    if (value[value.length - 1] == 0) {
                        // 结束了
                        value = value.slice(0, value.length - 1);
                        end = true;
                    }
                    // 接收的数据是增加的字符串，是一个delta
                    let str_content = new TextDecoder().decode(value);
                    callback(str_content);
                    if (end) {
                        abortCtrl.abort();  // 停止fetch，否则服务器一直还在保持连接
                        break;
                    }
                }
            }
        } else {
            console.error('Failed to send data:', response.status);
        }
    } catch (error) {
        console.error('Error sending data:', error);
    }
}
/*
```python
from fastapi import FastAPI, UploadFile, File
from starlette.responses import JSONResponse
from pathlib import Path
import hashlib
import os
app = FastAPI()
if not Path("uploaded_files").is_dir():
    os.makedirs("uploaded_files")

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    file_contents = await file.read()
    md5_hash = hashlib.md5(file_contents)
    md5_filename = md5_hash.hexdigest()
    file_path = Path("uploaded_files") / md5_filename

    if not file_path.is_file():
        with open(file_path, "wb") as buffer:
             buffer.write(file_contents)
    return JSONResponse(content={"filename": str(file_path)})
```

*/
export async function uploadFile(url:string, f:File|string|{[key:string]:File}){
    const formData = new FormData();
    if(f instanceof File){
        formData.append("file", f);
    }else if(typeof(f)=='object'){
        for( let m in f){
            formData.append(m,f[m]);
        }
    }
    else{
        const fileBuffer = fs.readFileSync(f);        
        if(fileBuffer){
            let blob = new Blob([fileBuffer.buffer], { type: "octet/stream" });
            formData.append('file', blob, path.basename(f)+'.'+path.extname(f));
        }
    }

    let response = await fetch(url, {
        method: 'POST',
        // headers: {
        //   "Content-Type": f.type,
        // },
        body: formData
      });
    let oo = response.ok;
    let ret = await response.json();
    console.log('response:', ret);
    return ret;//{fileid}
}

/**
 * 把一个base64的图片转成nodejs的Buffer对象
 */
function base64ToBuffer(base64: string): any {
    let base64Data = base64.replace(/^data:image\/\w+;base64,/, "");
    let dataBuffer = Buffer.from(base64Data, 'base64');
    return dataBuffer;
}

interface SDResponse {
    images: string[];
}

/**
 * 请求sd服务器，获取一个图片
 */
export async function getSkyPanoramaImage(prompt: string, width: number) {
    let url = AIConfig.SkyImageSvUrl;
    try {
        let res = await post(url,
            {
                width: width, height: width / 2,
                prompt: prompt,
                alwayson_scripts: {
                    "Asymmetric tiling": {
                        args: [true, true, false, 0, -1] //[active,tilex,tiley,start,stop]
                    }
                }
            } as ISDAPIBody) as string;
        let data = JSON.parse(res) as SDResponse;
        let buff = base64ToBuffer(data.images[0]);
        return buff;
    } catch (e) {
        throw '异常：可能天空图生成服务器没有启动';
    }
}

export async function getImage(prompt: string, width = 512, height = 512, alpha=false,reuse: boolean = true) {
    let url = AIConfig.ImageSvUrl;
    let body = { width, height, prompt } as ISDAPIBody;
    if(alpha){
        body.script_name="AddAlpha";
        body.script_args=[true];
        body.trans = true;
    }
    reuse && (body.reuse = reuse);
    let res: any = await LayaAPI.create().laya_text2img(body);
    if(res && res.code === LayaAPICode.CodeSuccess && res.data.status == LayaAPIStatus.TaskStatusDone){
        let images: string[] = res.data.images;
        return LayaAPI.formatImageUrl(images[0]);
    }
    return null;
}

export async function getImages(desc:ISDAPIBody, alpha=false, tilex=false, tiley=false, panorama=false):Promise<string[]> {
    let url = AIConfig.ImageSvUrl;
    let body = desc;
    if(alpha){
        body.script_name="AddAlpha"
        body.script_args=[true]
    }

    if(tilex||tiley){
        body.alwayson_scripts= {
            "Asymmetric tiling": {
                args: [true, tilex, tiley, 0, -1] //[active,tilex,tiley,start,stop]
            }
        }
    }

    if(panorama){
        url = AIConfig.SkyImageSvUrl;
        body.prompt+=',<lora:latentlabs360_v01:1>';
        //body.height = body.width/2;
    }

    //埋点： generateimage 开始
    Thinkingdata.ins.track("genImageStart", {prompt:desc.prompt,count:desc.batch_size});
    let st=Date.now();
    let res: any = await LayaAPI.create().laya_text2img(body);
    let dt = (Date.now()-st)/1000;
    Thinkingdata.ins.track("genImageEnd", {duration:dt});
    //埋点： generateimage 结束
    if(!res) return null;
    if(res.code !== LayaAPICode.CodeSuccess){
        console.error('server error:',res);
        return null;
    }
    if(res.data.status == LayaAPIStatus.TaskStatusDone){
        let images: string[] = res.data.images;
        return images.map((img: string) => LayaAPI.formatImageUrl(img));
    }
    if(res.data.status == LayaAPIStatus.TaskStatusNeedUsageCount){
        if(await 点数不够(LayaAPIAction.text_to_image)){
            //充值成功了
            return await getImages(desc, alpha, tilex, tiley, panorama);
        }
    }
    if( res.data.status==LayaAPIStatus.TaskStatusSubscriptionExpired){
        if(await 服务过期(LayaAPIAction.text_to_image)){
            return await getImages(desc, alpha, tilex, tiley, panorama);
        }
    }
    reportQueryError(res.data.status);
    return null;
}


export async function img2img(img:string, maskimg:string, prompt: string, otherargs:any, tilex=false, tiley=false):Promise<string[]> {
    let imgstr = img;
    if (img.startsWith('data:image/')) {
        //base64
    } else {
        imgstr = await IDEAgent.inst.load_asset_img_base64(img)
    }
    if (!imgstr) {
        console.log("load image failed");
        return null;
    }

    let maskstr = maskimg;
    if (maskstr) {
        if (maskimg.startsWith('data:image/')) {
        } else {
            maskstr = await IDEAgent.inst.load_asset_img_base64(maskimg)
        }
    }

    
    let url = AIConfig.i2iSvUrl;
    let body = {prompt, init_images:[imgstr], mask:maskstr, ...otherargs,trans:true};
    if(otherargs.alpha){
        body.script_name="AddAlpha"
        body.script_args=[true]
    }
    if(tilex||tiley){
        body.alwayson_scripts= {
            "Asymmetric tiling": {
                args: [true, tilex, tiley, 0, -1] //[active,tilex,tiley,start,stop]
            }
        }
    }
    //埋点： image2image 开始
    Thinkingdata.ins.track("img2imgStart", {prompt});
    let st = Date.now();
    let res: any = await LayaAPI.create().laya_img2img(body);
    let dt = (Date.now()-st)/1000;
    Thinkingdata.ins.track("img2imgEnd", {duration:dt});
    //埋点： image2image 结束
    if(!res) return null;
    if(res.code !== LayaAPICode.CodeSuccess){
        console.error('server error:',res);
        return null;
    }

    if(res.data.status == LayaAPIStatus.TaskStatusDone){
        let images: string[] = res.data.images;
        return images.map((img: string) => LayaAPI.formatImageUrl(img));
    }
    if(res.data.status == LayaAPIStatus.TaskStatusNeedUsageCount){
        if(await 点数不够(LayaAPIAction.image_to_image)){
            return await img2img(img, maskimg, prompt, otherargs, tilex, tiley);
        }
    }
    if( res.data.status==LayaAPIStatus.TaskStatusSubscriptionExpired){
        if(await 服务过期(LayaAPIAction.image_to_image)){
            return await img2img(img, maskimg, prompt, otherargs, tilex, tiley);
        }
    }
    reportQueryError(res.data.status);
    return null;
}


/**
 * chatgpt的调用和返回格式
 * @param msg
 * @param stream
 */
export async function chatgpt_chat(system:string, msg: string, retcb:(str:string)=>void,stream?:boolean) {
}

/**
 * 多轮对话
 * @param system
 * @param msg
 * @param retcb
 * @param stream
 * @returns
 */
export async function chatgpt_chats(message:{role:'system'|'user'|'assistant',content:string}[], retcb:(str:string)=>void,stream?:boolean): Promise<any> {
}

async function getAuidoUrl(data:{name:string,duration:number,temperature:number,mode:number, refMusic?:string}):Promise<string>{
    let url:string=null;
    let refid:string=null;
    if(data.refMusic){
        data.mode=0;
        //上传音乐文件,得到id
        let ret = await uploadFile(AIConfig.MusicGenUploadUrl,data.refMusic)
        if(ret){
            refid = ret.fileid;
        }
    }

    let test=false;
    if(test){
        //翻译
        let prompt = await AIUtils.translation(data.name);
        let postdata = {
            name:prompt,
            duration:data.duration,
            temperature:data.temperature,
            mode:data.mode,
            reference:refid,
        };
        return await post(AIConfig.MusicGenTestUrl,postdata) as string;
    }else{
        //埋点： generateMusic 开始
        Thinkingdata.ins.track("genMusicStart", {prompt:data.name});
        let st = Date.now();
        let res: any = await LayaAPI.create().laya_generate_music(data.name,data.duration,data.temperature,data.mode, refid);
        let dt = (Date.now()-st)/1000;
        Thinkingdata.ins.track("genMusicEnd", {duration:dt});
        //埋点： generateMusic 结束
        if( !res || res.code !== 0 ){
            return null;
        }
        if(res.data.status == LayaAPIStatus.TaskStatusDone){
            return res.data.url;
        }else{
            if(res.data.status == LayaAPIStatus.TaskStatusNeedUsageCount){
                if( await 点数不够(LayaAPIAction.generate_music)){
                    //充值完成
                    return await getAuidoUrl(data);
                }else{
                    return null;
                }
            }
            if( res.data.status==LayaAPIStatus.TaskStatusSubscriptionExpired){
                if(await 服务过期(LayaAPIAction.generate_music)){
                    return await getAuidoUrl(data);
                }else
                    return null;
            }        

            reportQueryError(res.data.status);
        }
    }
    return null;
}

export async function getAudio(data:{name:string,duration:number,temperature:number,mode:number, refMusic?:string}): Promise<ArrayBuffer>{
    let url = await getAuidoUrl(data);
    if(!url){
        return null;
    }
    let response = await fetch(LayaAPI.formatImageUrl(url));
    if(!response.ok){
        console.error('下载音乐失败！');
        return null;
    }
    return await response.arrayBuffer();
}


export async function parseCmd(cmd:string,userinfo:any,replaceUrl:string): Promise<any> {
    let retobj:any={};
        try{
            let url = replaceUrl??AIConfig.cmdParserUrl;
            let ret = await post(url, {data:{msg:cmd,userinfo}}) as string
            let jsonobj = JSON.parse(ret);
            retobj = jsonobj.data;
        }catch(e){
            retobj.label='其他';
            retobj.value=1.0;
        }
    return retobj;
}

export async function addLog(cmd:any,userinfo:any,replaceUrl:string): Promise<any> {
    try{
        let url = replaceUrl??AIConfig.cmdParseLogUrl;
        let ret = await post(url, {data:{msg:cmd,userinfo,onlylog:true}}) as string
    }catch(e){
    }
}
